1 : <?php
2 : /*--------------------------------------------------------------------------+
3 : This file is part of eStudy.
4 : common/class.data.inc.php
5 : - Modulgruppe: Framework
6 : - Beschreibung: Validierung von Daten mittels PCRE.
7 : Spezielle Datenklassen können von dieser Klasse abgeleitet werden.
8 : - Version: 0.1, 30/04/05
9 : - Autor(en): Udo Güngerich <udo.guengerich@mni.fh-giessen.de>
10 : Andreas Nolden <andreas.nolden@ei.fh-giessen.de>
11 : +---------------------------------------------------------------------------+
12 : This program is free software; you can redistribute it and/or
13 : modify it under the terms of the GNU General Public License
14 : as published by the Free Software Foundation; either version 2
15 : of the License, or any later version.
16 : +---------------------------------------------------------------------------+
17 : This program is distributed in the hope that it will be useful,
18 : but WITHOUT ANY WARRANTY; without even the implied warranty of
19 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 : GNU General Public License for more details.
21 : You should have received a copy of the GNU General Public License
22 : along with this program; if not, write to the Free Software
23 : Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24 : +--------------------------------------------------------------------------*/
25 : /**
26 : * Validierung von Daten mittels PCRE.
27 : * Spezielle Datenklassen können von dieser Klasse abgeleitet werden.
28 : * @version 0.1, 30/04/05
29 : * @author Udo Güngerich <udo.guengerich@mni.fh-giessen.de>
30 : * @author Andreas Nolden <andreas.nolden@ei.fh-giessen.de>
31 : * @package eStudy.Framework
32 : */
33 : /** Data class (general)
34 : purpose: To encapsule data validation inside a proper class hierarchy
35 : Specified data classes can be derived from this one.
36 : Validation happens via PCRE (Perl compatible regular expressions)
37 : Any wrong (invalid) data will be logged!
38 : Udo G. -- 2005-30-04
39 : Andi N.
40 : */
41 : class Data {
42 : var $data; // Contains the actual data ;)
43 : var $dataBase = 'mysql'; // Type of the database
44 : var $regex = '/^$/'; // regular expression used best
45 : // to check whether this Data is valid input
46 : var $backRefs = NULL; // array with the backticks/backrefs from preg_match
47 : var $alarmbehaviour = 'null'; // what to do in hurlAlarm()
48 : // possible: email, file, null
49 : var $email; // email of person to be notified in case
50 : // of malign input
51 : var $onMatchValid = true;
52 : var $malignCheck = 1; // 1=>check if not valid, 2=>check if valid, 3=> check anyway, 0 no check
53 : var $malignanceMessage = NULL; // log-string, built solely for convenience
54 : var $isChecked = false; // while we don't change the data we don't need to check for malignity
55 : // on every call of isValid, to*, ... especially we don't need to log all the time...
56 : var $matches = NULL; // true if regex matches...
57 : var $malignRegExp = array("toHTML" => array("simpleXSS" => "/((\%3C)|<)((\%2F)|\/)*[a-z0-9\%]+((\%3E)|>)/ix", "imgSrcXssAttack" => "/((\%3C)|<)((\%69)|i|(\%49))((\%6D)|m|(\%4D))((\%67)|g|(\%47))[^\n]+((\%3E)|>)/i", "anyTags" => "/((\%3C)|<)[^\n]+((\%3E)|>)/i"), "toMysql" => array("sqlMetaCharacters" => "/((\%3D)|(=))[^\n]*((\%27)|(\')|(\-\-)|(\%3B)|(;))/i", "typicalSqlInjection" => "/\w*((\%27)|(\'))\s*((\%6F)|o|(\%4F))((\%72)|r|(\%52))/ix", "sqlUnionInjection" => "/((\%27)|(\'))\s*union/ix"), "toFileName" => array("directoryTraversal" => "/\.\.\//"));
58 : var $malignance = NULL; // Will be set like malignRegExp if any input is found
59 : // to be a possible attack.
60 : var $isGPCData = true; // true, if data comes from GET/POST/COOKIE
61 : /// Constructor setting default values...
62 : function Data($newData = "", $newEmail = "", $isGPCData = true) {
63 : // This class is quite useless with magic_quotes_runtime on,
64 : // so we only work with magic_quotes_runtime off!
65 0 : if (get_magic_quotes_runtime()) $this->hurlAlarm(get_class($this) .': cannot work under these circumstances! Unset magic_quotes_runtime if you dare!');
66 0 : $this->gpcUnescape();
67 0 : $this->data = $newData;
68 0 : $this->email = $newEmail;
69 0 : $this->isGPCData = $isGPCData;
70 0 : }
71 : /// This method shall check the form of the data
72 : /// for validity using a regex.
73 : function isValid() {
74 : /// In this general case, nothing will be allowed,
75 : /// to enforce people using a subclass of Data
76 : // $regex is by default '^$'
77 : // allowing only the empty string
78 0 : $matches = preg_match($this->regex, $this->data, $this->backRefs);
79 0 : if ($this->onMatchValid == false) $matches = !$matches;
80 0 : $this->matches = $matches;
81 0 : foreach($this->malignRegExp as $r => $arr) $this->isMalign($r);
82 0 : return (bool)$matches;
83 : }
84 : function getBackRefs() {
85 0 : return $this->backRefs;
86 : }
87 : function setOnMatchValid($bool) {
88 0 : $this->onMatchValid = $bool;
89 0 : }
90 : // for clean Data
91 : function setData($data) {
92 0 : $this->data = $data;
93 0 : $this->isChecked = false;
94 0 : }
95 : // for dirty eventually slashed data from outside...
96 : function setGPCData($data) {
97 0 : $this->data = $data;
98 0 : $this->data = $this->gpcUnescape();
99 0 : $this->isChecked = false;
100 0 : }
101 : /// Hurling an alarm to responsible person if anyone
102 : /// tries to input anything fishy (by mistake or on purpose,
103 : /// we cannot know).
104 : function hurlAlarm($message) {
105 : // Check if any possible exploit is aimed
106 : // email responsible person and/or log to file
107 : // return whether alarm succeeded
108 0 : switch ($this->alarmbehaviour) {
109 0 : case 'email':
110 : // email to somebody
111 0 : mail($this->email, 'Alarm from Data '.$message);
112 0 : break;
113 :
114 0 : case 'file':
115 : // log to file
116 0 : $this->logToFile($message);
117 0 : break;
118 :
119 0 : case 'null':
120 : // keep incident secret
121 0 : break;
122 :
123 0 : case 'debug':
124 0 : echo Data::toHTML($message);
125 0 : break;
126 0 : }
127 0 : }
128 : function logToFile($str) {
129 0 : if (!$str == "") {
130 0 : $datei = "./log/".date("Ymd") .".log";
131 0 : $dat = fopen($datei, "a+");
132 0 : $logstr = $str."\n";
133 0 : fwrite($dat, $logstr);
134 0 : @fclose($dat);
135 0 : }
136 0 : }
137 : /// Usually, this method will be used seldomly.
138 : /// Possible case is a special form requirement
139 : /// that does not occur often enough to justify
140 : /// writing a subclass.
141 : function setRegex($regex) {
142 0 : $this->regex = $regex;
143 0 : }
144 : function isMalign($dataDestination) {
145 : //if ($this->isChecked == true) return;
146 : // This needs to be more sophisticated, we can check for different types
147 : // of attacks and checked for html does not include check for sql...
148 0 : $this->isChecked = true;
149 0 : $attackTypes = null;
150 0 : if (($this->malignCheck == 1 AND $this->matches == false) OR ($this->malignCheck == 2 AND $this->matches == true) OR $this->malignCheck == 3) {
151 0 : foreach($this->malignRegExp[$dataDestination] as $name => $d) {
152 0 : if (preg_match($d, $this->data)) {
153 0 : $this->malignance[$dataDestination][$name] = $name;
154 0 : }
155 0 : }
156 0 : if ($this->malignance != NULL) {
157 0 : $attackTypes = "";
158 0 : foreach($this->malignance as $mkey => $mvalue) $attackTypes.= "$mkey: ".implode(", ", $mvalue) ."\n";
159 0 : $this->malignanceMessage = date("D d.m.Y H:i:s") ." -> ".get_class($this) .":\n".$attackTypes." detected in (quoted >>STRING<<):\n\n\t>>".$this->data."<<\n\n FROM ".$_SERVER['REMOTE_ADDR'].", userID:".$_SESSION['userID']."\n"."Scriptname: ".$_SERVER['SCRIPT_FILENAME']."\n\n"; //REQUEST was:\n";//.var_dump($_REQUEST)."\n";
160 0 : $this->hurlAlarm($this->malignanceMessage);
161 0 : }
162 0 : }
163 0 : return ($attackTypes == NULL);
164 : }
165 : /// Prepare (possibly malicious) data for output
166 : /// via HTML. Therefor escaping special sequences
167 : function toHTML($data = false, $isGPCData = true) {
168 2 : global $settings;
169 : // If $data is an array, apply this function on every element
170 2 : if (is_array($data)) {
171 0 : foreach ($data as $key=>$value) {
172 0 : if (isset($this)){ // Function has been called via an object of this class
173 0 : $data[$key] = $this->toHTML($value);
174 0 : } else { // Function has been called statically
175 0 : $data[$key] = Data::toHTML($value);
176 : }
177 0 : }
178 0 : return $data; // Every element should be allright
179 : }
180 : // if called non-static
181 2 : if ($data === false && isset($this) && is_a($this, "Data")) {
182 0 : $data = $this->data;
183 : // secure output taken from somewhere before
184 : // displaying
185 0 : $this->isMalign('toHTML');
186 2 : } elseif ($isGPCData) {
187 2 : $data = Data::gpcUnescape($data);
188 2 : }
189 2 : if ( version_compare( PHP_VERSION, "5.2.3", ">=" ) === true ) {
190 2 : return htmlentities($data, ENT_COMPAT, $settings["charset"], false);
191 : } else {
192 0 : return htmlentities($data, ENT_COMPAT, $settings["charset"]);
193 : }
194 : }
195 : /// Prepare (possibly malicious) data for inserting
196 : /// into database. Therefor escaping special sequences.
197 : function toMysql($data = false, $isGPCData = true) {
198 14 : global $db;
199 : // if called non-static
200 14 : if ($data === false && isset($this) && is_a($this, "Data")) {
201 0 : $data = $this->data;
202 : // secure output taken from somewhere before
203 : // displaying
204 0 : $this->isMalign('toMysql');
205 14 : } elseif ($isGPCData) {
206 14 : $data = Data::gpcUnescape($data);
207 14 : }
208 14 : if (is_resource($db->dbh)) return mysql_real_escape_string($data, $db->dbh);
209 0 : return mysql_real_escape_string($data);
210 : }
211 : function toFileName($data = false) {
212 : // if called non-static
213 0 : if ($data === false && isset($this) && is_a($this, "Data")) {
214 0 : $data = $this->data;
215 0 : $this->isMalign('toFileName');
216 0 : } else {
217 0 : $data = Data::gpcUnescape($data);
218 : }
219 0 : return escapeshellarg($data);
220 : }
221 : /// Just checking for database type and choosing the right
222 : /// securing method.
223 : function toDb($data = false) {
224 0 : if ($data === false && isset($this) && is_a($this, "Data")) $data = $this->data;
225 0 : switch ($this->dataBase) {
226 0 : case 'mysql':
227 0 : return $this->toMysql();
228 0 : default:
229 0 : error("In ".$_SERVER['SCRIPT_FILENAME'].': '.get_class($this) .'::toDb: Not a valid database type');
230 : // Is there any way to
231 : // dynamically get the name of the script/object?
232 : // such as: "$thisname: Not a valid database type"
233 : // that would be handy
234 :
235 0 : }
236 0 : }
237 : /// Unescaping data for display if gpc_magic_quotes is on.
238 : /// Doing nothing if not.
239 : function gpcUnescape($data = false) {
240 14 : $gpc = true;
241 14 : if ($data === false && isset($this) && is_a($this, "Data")) {
242 0 : $data = $this->data;
243 0 : $gpc = $this->isGPCData;
244 0 : }
245 : // usescape before putting out...
246 14 : if (get_magic_quotes_gpc() && $gpc) return stripslashes($data);
247 0 : else return $data;
248 : }
249 : /// Changing the type of the database.
250 : function setDatabase($dataBase) {
251 0 : $this->dataBase = $dataBase;
252 0 : }
253 : /// Knowing the type of the database.
254 : function getDatabase() {
255 0 : return $this->dataBase;
256 : }
257 : }
258 : class IntegerNumberData extends Data {
259 : var $regex = '/^-?[0-9]+$/';
260 : function IntegerNumberData($newData = "", $newEmail = "") {
261 0 : parent::Data($newData, $newEmail);
262 0 : }
263 : }
264 : class FloatNumberData extends Data {
265 : var $regex = '/^-?[0-9]+(,[0-9]+)?$/';
266 : function FloatNumberData($newData = "", $newEmail = "") {
267 0 : parent::Data($newData, $newEmail);
268 0 : }
269 : }
270 : class AnyData extends Data {
271 : var $regex = '/^.*$/';
272 : function AnyData($newData = "", $newEmail = "") {
273 0 : parent::Data($newData, $newEmail);
274 0 : }
275 : }
276 : class LoginData extends Data {
277 : var $regex = '/^[a-zA-Z]{2,4}[0-9]{2,8}$/';
278 : function LoginData($newData = "", $newEmail = "") {
279 0 : parent::Data($newData, $newEmail);
280 0 : }
281 : // overload isValid to use Authentification.inc.php ?
282 :
283 : }
284 : class EmailData extends Data {
285 : var $regex = '/^[^@\s]+@([-a-z0-9]+\.)+[a-z]{2,}$/i';
286 : function EmailData($newData = "", $newEmail = "") {
287 0 : parent::Data($newData, $newEmail);
288 0 : }
289 : }
290 : class MemberEmailData extends Data {
291 : var $regex = '/^[^@\s]+@([-a-z0-9]+\.)+[a-z]{2,}$/i';
292 : function MemberEmailData($newData = "", $newEmail = "") {
293 0 : parent::Data($newData, $newEmail);
294 0 : }
295 : }
296 : class DayTimeData extends Data {
297 : var $regex = '/^([0-9]{1,2}):([0-9]{1,2})$/';
298 : function DayTimeData($newData = "", $newEmail = "") {
299 0 : parent::Data($newData, $newEmail);
300 0 : }
301 : function isValid() {
302 0 : $val = parent::isValid();
303 0 : if ($val == true) {
304 0 : $backref = parent::getBackRefs();
305 : // backrefs are set if parent::isValid is true ...
306 0 : if ($backref[1] >= 0 && $backref[1] < 24 && $backref[2] >= 0 && $backref[2] < 60) $val = true;
307 0 : else $val = false;
308 0 : }
309 0 : return ($val);
310 : }
311 : }
312 : class GermanDateData extends Data {
313 : var $regex = '/^([0-9]{1,2})\.([0-9]{1,2})\.([0-9]{4})$/';
314 : function GermanDateData($newData = "", $newEmail = "") {
315 0 : parent::Data($newData, $newEmail);
316 0 : }
317 : function isValid() {
318 0 : $val = parent::isValid();
319 0 : if ($val == true) {
320 0 : $backref = parent::getBackRefs();
321 : // backrefs are set if parent::isValid is true ...
322 0 : $val = checkdate($backref[2], $backref[1], $backref[3]);
323 0 : }
324 0 : return ($val);
325 : }
326 : }
|